/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cn.ac.iie.di.ban.news.server;

import cn.ac.iie.di.ban.news.server.check.JsonChecker;
import cn.ac.iie.di.ban.news.server.handlers.HelloHandler;
import cn.ac.iie.di.ban.news.server.handlers.data.DataHandler;
import cn.ac.iie.di.ban.news.server.handlers.meta.DispatchHandler;
import cn.ac.iie.di.ban.news.server.handlers.meta.MetaCollectHandler;
import cn.ac.iie.di.ban.news.server.runtime.RuntimeEnv;
import cn.ac.iie.di.ban.news.server.runtime.RuntimeEnvException;
import cn.ac.iie.di.ban.news.server.speed.MultiKeySpeedLimiter;
import cn.ac.iie.di.ban.news.server.speed.SpeedLimiterAdaptor;
import cn.ac.iie.di.ban.news.server.speed.TotalSpeedLimiter;
import cn.ac.iie.di.ban.news.server.util.DataSender;
import cn.ac.iie.di.ban.news.server.util.FileAppender;
import cn.ac.iie.di.ban.news.server.util.HostInfoKeeper;
import cn.ac.iie.di.ban.news.server.util.HttpUtils;
import cn.ac.iie.di.ban.news.server.util.KeyInfoKeeper;
import cn.ac.iie.di.ban.news.server.util.tracer.DataTraceAgent;
import cn.ac.iie.di.ban.news.server.util.tracer.DataTracer;
import cn.ac.iie.di.ban.news.server.util.tracer.PostDataTracer;
import cn.ac.iie.di.ban.news.server.util.tracer.RmqDataTracer;
import cn.ac.iie.di.commons.httpserver.framework.server.HttpsServer;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

/**
 *
 * @author zwlin
 */
public class NewsServerStartup {

    private static final Logger logger;

    static {
        PropertyConfigurator.configure(ClassLoader.getSystemClassLoader().getResource("log4j.properties"));
        logger = Logger.getLogger(NewsServerStartup.class);
    }
    private static Timer metaHeatbeatTimer = null;
    private static KeyInfoKeeper keyKeeper;
    private static HostInfoKeeper hostKeeper;
    private static JsonChecker jsonChecker;

    public static void main(String[] args) throws RuntimeEnvException, Exception {
        try {

            RuntimeEnv.init();

            logger.info("trying to build https server...");
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("news_server_private.keystore");
            HttpsServer server = new HttpsServer(
                    "0.0.0.0",
                    RuntimeEnv.getParamAsInteger(RuntimeEnv.SERVER_PORT),
                    RuntimeEnv.getParamAsInteger(RuntimeEnv.SERVER_PARALLEL),
                    is,
                    "123456", "123456");
            if (RuntimeEnv.getParamAsBoolean(RuntimeEnv.SERVER_AUTH_TWO_WAY)) {
                server.setTrustStore(ClassLoader.getSystemResourceAsStream("news_client_public.keystore"), "123456");
            }
            server.init();
            logger.info("https server build successfully at port " + RuntimeEnv.getParamAsInteger(RuntimeEnv.SERVER_PORT));
            logger.info("tring to rec hello handler");
            server.registerContext("/hello");
            server.registerContextHandler("/hello", "/", HelloHandler.class);
            logger.info("reg hello handler successfully.");

            //if need meta server
            if (RuntimeEnv.getParamAsBoolean(RuntimeEnv.META_ACTIVATED)) {
                initMetaService(server);
            }

            //if need data server
            if (RuntimeEnv.getParamAsBoolean(RuntimeEnv.DATA_ACTIVATED)) {
                initDataService(server);
            }
            if (jsonChecker != null) {
                logger.info("trying to start jsonChecker...");
                jsonChecker.start();
                logger.info("jsonChecker start successfully.");
            }
            if (keyKeeper != null) {
                logger.info("trying to start key kepper...");
                keyKeeper.start();
                logger.info("key kepper start successfully.");
            }
            if (hostKeeper != null) {
                logger.info("trying to start host keeper...");
                hostKeeper.start();
                logger.info("host keeper start successfully.");
            }
            logger.info("trying to start server.");
            server.startup();
            logger.info("server started.");

            //start meta heartbeat if needed
            String[] metaUrls = RuntimeEnv.getParamAsStrings(RuntimeEnv.DATA_META_URLS);
            if (metaUrls != null && metaUrls.length != 0 && !metaUrls[0].isEmpty()) {
                startMetaHeatbeatTimer(metaUrls,
                        RuntimeEnv.getParamAsString(RuntimeEnv.DATA_HOST),
                        RuntimeEnv.getParamAsString(RuntimeEnv.DATA_TYPE),
                        RuntimeEnv.getParamAsInteger(RuntimeEnv.HEARTBEAT_INTERVAL));
            }
        } catch (Exception | Error e) {
            logger.fatal("unknow error appears!", e);
        }
    }

    private static void initMetaService(HttpsServer server) throws RuntimeEnvException, Exception {
        //init keepers
        hostKeeper = new HostInfoKeeper(
                RuntimeEnv.getParamAsInteger(RuntimeEnv.HEARTBEAT_INTERVAL) * 2);
        keyKeeper = new KeyInfoKeeper(
                Arrays.asList(RuntimeEnv.getParamAsStrings(RuntimeEnv.META_KEYINFO_HOSTS)),
                RuntimeEnv.getParamAsInteger(RuntimeEnv.HEARTBEAT_INTERVAL),
                false);

        //init handlers
        DispatchHandler.init(hostKeeper, keyKeeper);
        MetaCollectHandler.init(hostKeeper);

        //reg handlers
        logger.info("trying to reg dispatch handler...");
        server.registerContext("/dispatch");
        server.registerContextHandler("/dispatch", "/", DispatchHandler.class);
        logger.info("reg dispatch handler successfully.");
        logger.info("trying to reg meta handler...");
        server.registerContext("/meta");
        server.registerContextHandler("/meta", "/", MetaCollectHandler.class);
        logger.info("reg meta handler successfully.");
    }

    private static void initDataService(HttpsServer server) throws RuntimeEnvException, Exception {
        //key info keeper
        if (keyKeeper == null) {
            keyKeeper = new KeyInfoKeeper(
                    Arrays.asList(RuntimeEnv.getParamAsStrings(RuntimeEnv.META_KEYINFO_HOSTS)),
                    RuntimeEnv.getParamAsInteger(RuntimeEnv.HEARTBEAT_INTERVAL),
                    false);
        }
        String[] opinionFiled = RuntimeEnv.getParamAsStrings(RuntimeEnv.PUBLIC_OPINION_FILED);
        //rmq sender
        DataSender sender;
        String namesrv = RuntimeEnv.getParamAsString(RuntimeEnv.DATA_RMQ_NAMESRV);
        if (namesrv == null || namesrv.isEmpty()) {
            sender = null;
        } else {
            sender = new DataSender();
            String topic;
            for (String name : opinionFiled) {
                topic = RuntimeEnv.getParamAsString("data." + name + ".topic");
                if (topic != null && !topic.trim().isEmpty()) {
                    sender.regSender(
                            RuntimeEnv.getParamAsString("data." + name + ".type"),
                            RuntimeEnv.getParamAsString(RuntimeEnv.DATA_RMQ_NAMESRV),
                            RuntimeEnv.getParamAsString("data." + name + ".topic"),
                            RuntimeEnv.getParamAsTypeMap("data." + name + ".fields"));
                }
            }
        }

        //file appender
        FileAppender appender;
        String cacheDir = RuntimeEnv.getParamAsString(RuntimeEnv.DATA_CACHE_DIR);
        String targetDir = RuntimeEnv.getParamAsString(RuntimeEnv.DATA_TARGET_DIR);
        if (cacheDir == null || cacheDir.isEmpty()) {
            appender = null;
        } else {
            appender = new FileAppender(cacheDir, targetDir);
        }

        //datatype
        boolean isOnline = "online".equals(
                RuntimeEnv.getParamAsString(RuntimeEnv.DATA_TYPE).trim().toLowerCase());

        //jsonchecker
        jsonChecker = new JsonChecker(Arrays.asList(RuntimeEnv.getParamAsStrings(RuntimeEnv.DATA_CONFIG_HOSTS)),
                RuntimeEnv.getParamAsInteger(RuntimeEnv.CHECK_INTERVAL),
                false);
        //DataTracer
        DataTraceAgent traceAgent = new DataTraceAgent();
        DataTracer tracer = null;
        //post tracer
        String[] traceUrls = RuntimeEnv.getParamAsStrings(RuntimeEnv.DATA_TRACE_URLS);
        if (traceUrls == null || traceUrls.length == 0) {
        } else {
            tracer = new PostDataTracer(RuntimeEnv.getParamAsString(RuntimeEnv.DATA_TYPE).trim().toLowerCase(), Arrays.asList(traceUrls));
            traceAgent.reg("PostTracer", tracer);
            tracer = null;
        }
        //rmq tracer
        String traceNamesrv = RuntimeEnv.getParamAsString(RuntimeEnv.DATA_TRACE_NAMESRV);
        String traceTopic = RuntimeEnv.getParamAsString(RuntimeEnv.DATA_TRACE_TOPIC);
        if (traceNamesrv == null
                || traceTopic == null
                || traceNamesrv.trim().isEmpty()
                || traceTopic.trim().isEmpty()) {
        } else {
            tracer = new RmqDataTracer(traceNamesrv.trim(), traceTopic.trim(), RuntimeEnv.getParamAsString(RuntimeEnv.DATA_TYPE).trim().toLowerCase());
            traceAgent.reg("RmqTracer", tracer);
            tracer = null;
        }

        logger.info("trying to init data handler...");
        //init handler
        DataHandler.init(sender, appender, keyKeeper, isOnline, jsonChecker, traceAgent);
        logger.info("init data handler successfully with "
                + (sender == null ? "no dataSender" : "dataSender") + " , "
                + (appender == null ? "no file appender" : "file appender") + " , "
                + (isOnline ? "online" : "test") + " server , "
                //+ (jsonChecker == null ? "no content checker" : "jsong content checker" + " , ")
                + traceAgent.getTracerDetail());

        //speed limitor
        SpeedLimiterAdaptor speedLimiterAdaptor = new SpeedLimiterAdaptor();
        if (RuntimeEnv.getParamAsBoolean(RuntimeEnv.DATA_SPEED_LIMIT_TOTAL_ACTIVATED)) {
            speedLimiterAdaptor.regSpeedLimiter(new TotalSpeedLimiter(RuntimeEnv.getParamAsInteger(RuntimeEnv.DATA_SPEED_LIMIT_TOTAL_TPS)));
        }
        if (RuntimeEnv.getParamAsBoolean(RuntimeEnv.DATA_SPEED_LIMIT_EACH_ACTIVATED)) {
            MultiKeySpeedLimiter limitor = new MultiKeySpeedLimiter(RuntimeEnv.getParamAsInteger(RuntimeEnv.DATA_SPEED_LIMIT_EACH_TPS));
            limitor.limit(RuntimeEnv.getParamAsIntegerMap(RuntimeEnv.DATA_SPEED_LIMIT_EACH_DETAIL));
        }
        speedLimiterAdaptor.start();
        DataHandler.setLimiter(speedLimiterAdaptor);

        //reg handler
        logger.info("trying to reg data handler...");
        server.registerContext("/data");
        server.registerContextHandler("/data", "/", DataHandler.class);
        logger.info("data handler reg successfully.");
        metaHeatbeatTimer = new Timer("MetaHeartbeatTimer");
    }

    private static void startMetaHeatbeatTimer(final String[] metaUrls, final String host, final String type, final int heartbeatIntevalSecond) {

        if (metaHeatbeatTimer == null) {
            return;
        }
        HashMap<String, String> param = new HashMap<>();
        param.put("type", type);
        param.put("host", host);

        for (String metaUrl : metaUrls) {
            try {
                HttpUtils.Result result = HttpUtils.httpsPost(metaUrl, param);
                if (result.statusCode != HttpServletResponse.SC_OK) {
                    logger.error("fail to send heatbeat to " + metaUrl
                            + " , because: " + result.reasonPhrase);
                }
            } catch (Exception ex) {
                logger.error("cannot send heartbeat to " + metaUrl + ".", ex);
            }
        }
        metaHeatbeatTimer.schedule(new TimerTask() {
            Logger logger = Logger.getLogger("MetaHeatBeatTimer");

            @Override
            public void run() {
                HashMap<String, String> param = new HashMap<>();
                param.put("type", type);
                param.put("host", host);

                for (String metaUrl : metaUrls) {
                    try {
                        HttpUtils.Result result = HttpUtils.httpsPost(metaUrl, param);
                        if (result.statusCode != HttpServletResponse.SC_OK) {
                            logger.error("fail to send heatbeat to " + metaUrl
                                    + " , because: " + result.reasonPhrase);
                        }
                    } catch (Exception ex) {
                        logger.error("cannot send heartbeat to " + metaUrl + ".", ex);
                    }
                }
            }
        }, 5000, heartbeatIntevalSecond * 1000);
        logger.info("meta heart beat timer started.");
    }

    public static void stopMetaHeatbeatTimer() {
        if (metaHeatbeatTimer != null) {
            metaHeatbeatTimer.cancel();
            metaHeatbeatTimer = null;
        }
    }

}
